package org.infodavid.common.reflect.impl;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.infodavid.common.reflect.InstantiationHelper;

/**
 * The Class ScannotationInstantiationHelper.
 */
@SuppressWarnings("rawtypes")
public final class PropertyInstantiationHelper extends InstantiationHelper {

	/** The Constant log. */
	private static final Log LOGGER = LogFactory.getLog(PropertyInstantiationHelper.class);// NOPMD

	/** The dynamic implementations. */
	private final HashMap<Class,Object> dynamicImplementations = new HashMap<Class,Object>();

	/**
	 * Instantiates a new reflect utility.
	 */
	private PropertyInstantiationHelper() {
		super();
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.reflect.InstantiationHelper#newInstance(java.lang.Class)
	 */
	@Override
	public <T> T newInstance(final Class<T> reference) throws Exception {
		return newInstance(reference, null);
	}

	/**
	 * New instance.
	 * @param <T> the type
	 * @param reference the reference
	 * @param loader the loader
	 * @return the T
	 * @throws Exception the exception
	 */
	@SuppressWarnings("unchecked")
	private <T> T newInstance(final Class<T> reference, final ClassLoader loader) throws Exception {
		T instance = (T)dynamicImplementations.get(reference);

		if (instance == null) {
			final Set<Class> classes = new HashSet<Class>();

			InstantiationUtil.analyseClassPaths(reference, classes, loader);

			if (classes.isEmpty()) {
				LOGGER.warn("No implementation defined for class: " + reference);
			}
			else {
				final Stack<Class> implementations = new Stack<Class>();

				InstantiationUtil.processClasses(reference, classes, implementations);

				instance = InstantiationUtil.newInstance(reference, implementations);
			}

			if (instance == null) {
				throw new NoClassDefFoundError("No implemenation found for class:" + reference.getName());
			}
		}

		return instance;
	}

	/**
	 * New instances.
	 * @param <T> the generic type
	 * @param reference the reference
	 * @param loader the loader
	 * @return the collection
	 * @throws Exception the exception
	 */
	public <T> Collection<T> newInstances(final Class<T> reference, final ClassLoader loader)
	    throws Exception {
		final Set<Class> classes = new HashSet<Class>();
		Collection<T> instances = null;

		InstantiationUtil.analyseClassPaths(reference, classes, loader);

		if (classes.isEmpty()) {
			LOGGER.warn("No implementation defined for class: " + reference);
		}
		else {
			final Stack<Class> implementations = new Stack<Class>();

			InstantiationUtil.processClasses(reference, classes, implementations);

			instances = InstantiationUtil.newInstances(reference, implementations);
		}

		return instances;
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.reflect.InstantiationHelper#newInstances(java.lang.Class)
	 */
	@Override
	public <T> Collection<T> newInstances(final Class<T> reference) throws Exception {
		return newInstances(reference, null);
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.reflect.InstantiationHelper#register(java.lang.Class, java.lang.Object)
	 */
	@Override
	public void register(final Class<?> clazz, final Object impl) {
		dynamicImplementations.put(clazz, impl);
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.reflect.InstantiationHelper#unregister(java.lang.Class)
	 */
	@Override
	public void unregister(final Class<?> clazz) {
		dynamicImplementations.remove(clazz);
	}
}
